/**
 * 
 */
package xiaojian.toolkit.bean.tlv.decode.decoders;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.bytebean.ByteFieldToDesc;
import xiaojian.toolkit.bean.bytebean.codec.BeanCodecUtil;
import xiaojian.toolkit.bean.bytebean.core.ByteFieldCodec;
import xiaojian.toolkit.bean.bytebean.core.ByteFieldDesc;
import xiaojian.toolkit.bean.bytebean.core.DecContextFactory;
import xiaojian.toolkit.bean.bytebean.core.DecResult;
import xiaojian.toolkit.bean.bytebean.core.FieldCodecCategory;
import xiaojian.toolkit.bean.bytebean.core.FieldCodecProvider;
import xiaojian.toolkit.bean.tlv.annotation.TLVAttribute;
import xiaojian.toolkit.bean.tlv.decode.TLVDecodeContext;
import xiaojian.toolkit.bean.tlv.decode.TLVDecodeContextFactory;
import xiaojian.toolkit.bean.tlv.decode.TLVDecoder;
import xiaojian.toolkit.bean.tlv.decode.TLVDecoderOfBean;
import xiaojian.toolkit.bean.tlv.meta.TLVFieldMetainfo;
import xiaojian.toolkit.bean.util.meta.Int2TypeMetainfo;
import xiaojian.toolkit.util.ByteUtils;


/**
 * @author hp
 *
 */
public class BeanMixedTLVDecoder implements TLVDecoderOfBean {

    private static final Logger LOG = 
    	LoggerFactory.getLogger(BeanMixedTLVDecoder.class);
    
    private	TLVDecodeContextFactory	tlvDecodeContextFactory;
	private	DecContextFactory	byteDecContextFactory;
	private	FieldCodecProvider	byteCodecProvider;
    
	private final BeanCodecUtil util = new BeanCodecUtil(new ByteFieldToDesc());

    /**
	 * @return the byteCodecProvider
	 */
	public FieldCodecProvider getByteCodecProvider() {
		return byteCodecProvider;
	}

	/**
	 * @param byteCodecProvider the byteCodecProvider to set
	 */
	public void setByteCodecProvider(FieldCodecProvider byteCodecProvider) {
		this.byteCodecProvider = byteCodecProvider;
	}

	/**
	 * @return the tlvDecodeContextFactory
	 */
	public TLVDecodeContextFactory getTlvDecodeContextFactory() {
		return tlvDecodeContextFactory;
	}

	/**
	 * @param tlvDecodeContextFactory the tlvDecodeContextFactory to set
	 */
	public void setTlvDecodeContextFactory(
			TLVDecodeContextFactory tlvDecodeContextFactory) {
		this.tlvDecodeContextFactory = tlvDecodeContextFactory;
	}

	/**
	 * @return the byteDecContextFactory
	 */
	public DecContextFactory getByteDecContextFactory() {
		return byteDecContextFactory;
	}

	/**
	 * @param byteDecContextFactory the byteDecContextFactory to set
	 */
	public void setByteDecContextFactory(DecContextFactory byteDecContextFactory) {
		this.byteDecContextFactory = byteDecContextFactory;
	}

	/* (non-Javadoc)
	 * @see com.skymobi.bean.tlv.decode.decoders.TLVDecoderOfBean#getDecodeContextFactory()
	 */
	public TLVDecodeContextFactory getDecodeContextFactory() {
		return tlvDecodeContextFactory;
	}

	public Object decode(int tlvLength, byte[] tlvValue, final TLVDecodeContext ctx) {
		
		if ( tlvValue.length < tlvLength ) {
			throw new RuntimeException("BeanMixedTLVDecoder, too few bytes.");
		}
		
		final Class<?> valueClazz = ctx.getValueType();
		if ( null == valueClazz ) {
			throw new RuntimeException("BeanMixedTLVDecoder, invalid value type.");
		}
		
		Object target = null;
		try {
			target = List.class.isAssignableFrom(valueClazz) 
					? new ArrayList<Object>() 
					: valueClazz.newInstance();
		} catch (InstantiationException e) {
			LOG.error("BeanMixedTLVDecoder:", e);
		} catch (IllegalAccessException e) {
			LOG.error("BeanMixedTLVDecoder:", e);
		}
		
		if (null == target) {
			throw new RuntimeException("BeanMixedTLVDecoder, can not create instance.");
		}
		
		{
			final int orgByteLen = tlvValue.length;
			
			tlvValue = decodeByteFields(tlvValue, valueClazz, target);
			tlvLength -= (orgByteLen - tlvValue.length);
		}
  
		decodeTLVFields(tlvLength, tlvValue, ctx, target);
		
		return target;
	}

	/**
	 * @param tlvLength
	 * @param tlvValue
	 * @param ctx
	 * @param target
	 */
	@SuppressWarnings("unchecked")
	private void decodeTLVFields(final int tlvLength, final byte[] tlvValue,
			final TLVDecodeContext ctx, final Object target) {
		int offset = 0;
		while ( offset + 8 <= tlvLength ) {
			
			final byte[] tagBytes = ArrayUtils.subarray(tlvValue, offset, offset + 4);
			final int tag = ctx.getNumberCodec().bytes2Int(tagBytes, 4);
			
			offset += 4;
			final byte[] lenBytes = ArrayUtils.subarray(tlvValue, offset, offset + 4);
			final int len = ctx.getNumberCodec().bytes2Int(lenBytes, 4);
			offset += 4;
			
			if ( len < 0 ) {
				LOG.error("tag {}'s len less than 0, bytes detail {}", tag,
						ByteUtils.bytesAsHexString(ArrayUtils.subarray(tlvValue, offset - 8, tlvValue.length ), 1024));
				throw new RuntimeException("tag [" + tag + "]'s len [" + len + "] less than 0");
			}
			
			final Int2TypeMetainfo typeMetainfo = ctx.getTypeMetainfo();
			final Class<?> type = typeMetainfo.find(tag);
			if ( null == type ) {
				// unknow tag, just ignore
				LOG.info("unknow tag:"+tag+", just ignore.");
				offset += len;
				continue;
			}
			
			final TLVDecoder decoder = ctx.getDecoderRepository().getDecoderOf(type);
			if ( null == decoder ) {
				// unknow tag, just ignore
				LOG.info("unknow decoder for [tag]:"+tag+",[type]:"+type+" just ignore.");
				offset += len;
				continue;
			}
			
			final byte[] valueBytes = ArrayUtils.subarray(tlvValue, offset, offset + len);
			offset += len;
			
			final TLVFieldMetainfo fieldMetainfo = ctx.getFieldMetainfo();
			final Field field = fieldMetainfo.get(tag);
			Object bean = null;
            try {
                bean = decoder.decode(len, valueBytes,
                        tlvDecodeContextFactory.createDecodeContext(type, field));
            }catch (RuntimeException e){
                LOG.error("Decode tag {}({}) error!",new String(Hex.encodeHex(tagBytes)).toUpperCase(),tag);
                throw e;
            }

            final TLVAttribute param  = field.getAnnotation(TLVAttribute.class);
            Class<?>	fieldType = param.type();
            if ( TLVAttribute.class.equals(fieldType)) {
            	fieldType = field.getType();
            }
			if (List.class.isAssignableFrom(fieldType)) {
				try {
					List<Object> list = (List<Object>)field.get(target);
					if ( null == list ) {
						list = new ArrayList<Object>();
						field.set(target, list);
					}
					list.add(bean);
				} catch (IllegalArgumentException e) {
					LOG.error("BeanMixedTLVDecoder:", e);
				} catch (IllegalAccessException e) {
					LOG.error("BeanMixedTLVDecoder:", e);
				}
			}
			else {
				try {
					field.set(target, bean);
				} catch (IllegalArgumentException e) {
					LOG.error("BeanMixedTLVDecoder:", e);
				} catch (IllegalAccessException e) {
					LOG.error("BeanMixedTLVDecoder:", e);
				}
			}
		}
	}

	/**
	 * @param tlvValue
	 * @param valueClazz
	 * @param target
	 * @return
	 */
	private byte[] decodeByteFields(final byte[] tlvValue, final Class<?> valueClazz,
			final Object target) {
		final List<ByteFieldDesc> desces = util.getFieldDesces(valueClazz);
		if (!desces.isEmpty()) {
			byte[] bytes = tlvValue;
			
			// include ByteField annotation
			final ByteFieldCodec anyCodec = byteCodecProvider
					.getCodecOf(FieldCodecCategory.ANY);

			for (ByteFieldDesc desc : desces) {

				final Field field = desc.getField();

				final Class<?> fieldClass = field.getType();

				final DecResult ret = anyCodec.decode(byteDecContextFactory
						.createDecContext(bytes, fieldClass, target, desc));
				final Object fieldValue = ret.getValue();

				bytes = ret.getRemainBytes();

				field.setAccessible(true);
				try {
					field.set(target, fieldValue);
				} catch (IllegalArgumentException e) {
					LOG.error("failed to set value [{}] to field[{}]",
							fieldValue, field);
				} catch (IllegalAccessException e) {
					LOG.error("failed to set value [{}] to field[{}]",
							fieldValue, field);
				}
			}
			
			//	modify len & value bytes
			return bytes;
		}
		else {
			return tlvValue;
		}
	}

}
